#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>

#define NIT_SHORT_NAMES
#include <nit/macros.h>
#include <nit/palloc.h>
#include <nit/list.h>
#include <nit/hset.h>
#include <nit/hmap.h>

#include "ort.h"

#define UUID_STR_LEN 36

int
ort_base_init(Ort_base *base, const char *path)
{
	base->path = path;
	base->path_size = strlen(path);
	base->stack = NULL;

	return  base->path_size + 1 <= PATH_MAX - UUID_STR_LEN &&
		hmap_init(&base->map, 0);
}

static void
map_free(void *key, void *storage, void *extra)
{
	Ort_dat *dat = storage;

	(void) key;
	(void) extra;

	if (dat->freeable)
		free(dat);
}

void
ort_base_dispose(Ort_base *base)
{
	hmap_dispose(&base->map, map_free, NULL);

	delayed_foreach(base->stack)
		free(base->stack);
}

Ort_base *
ort_base_new(const char *path)
{
	Ort_base *base = palloc(base);

	pcheck(base, NULL);

	if (!ort_base_init(base, path)) {
		free(base);
		return NULL;
	}

	return base;
}

void
ort_base_free(Ort_base *base)
{
	ort_base_dispose(base);
	free(base);
}

int
ort_dat_init(Ort_dat *dat, Ort_base *base, size_t size)
{
	dat->base = base;
	uuid_generate(dat->id);
	dat->size = size;
	dat->freeable = 0;

	return hmap_add(&base->map, dat->id, sizeof(dat->id),
			dat, &base->stack);
}

int
ort_dat_init0(Ort_dat *dat, Ort_base *base, size_t size)
{
	if (!ort_dat_init(dat, base, size))
		return 0;

	memset(dat->mem, 0, size);
	return 1;
}

void
ort_dat_revoke(Ort_dat *dat)
{
	hmap_remove(&dat->base->map, dat->id,
		    sizeof(dat->id), &dat->base->stack);
}

Ort_dat *
ort_dat_new(Ort_base *base, size_t size)
{
	Ort_dat *dat = malloc(sizeof(*dat) + size);

	pcheck(dat, NULL);

	if (!ort_dat_init(dat, base, size)) {
		free(dat);
		return NULL;
	}

	dat->freeable = 1;
	return dat;
}

Ort_dat *
ort_dat_new0(Ort_base *base, size_t size)
{
	Ort_dat *dat = ort_dat_new(base, size);

	pcheck(dat, NULL);
	memset(dat->mem, 0, size);
	return dat;
}

void
ort_dat_free(Ort_dat *dat)
{
	ort_dat_dispose(dat);
	free(dat);
}

Ort_ptr
ort_alloc(Ort_base *base, size_t size)
{
	Ort_dat *dat = ort_dat_new(base, size);

	pcheck(dat, NULL);
	return dat + 1;
}

Ort_ptr
ort_alloc0(Ort_base *base, size_t size)
{
	Ort_dat *dat = ort_dat_new0(base, size);

	pcheck(dat, NULL);
	return dat + 1;
}

void
ort_free(Ort_ptr mem)
{
	ort_dat_free(ort_dat(mem));
}

Ort_ptr
ort_init(Ort_base *base, void *dat, size_t size)
{
	if (!ort_dat_init(dat, base, size))
		return NULL;

	return ((Ort_dat *) dat + 1);
}

Ort_ptr
ort_init0(Ort_base *base, void *dat, size_t size)
{
	if (!ort_dat_init0(dat, base, size))
		return NULL;

	return ((Ort_dat *) dat) + 1;
}

void
ort_revoke(Ort_ptr mem)
{
	ort_dat_dispose(ort_dat(mem));
}

static FILE *
ort_file(Ort_base *base, uuid_t id, const char *mode)
{
	char str[36];
	char filename[PATH_MAX];
	FILE *fp;

	uuid_unparse_lower(id, str);
	strcpy(filename, base->path);
	strcpy(filename + base->path_size, str);

	pcheck(fp = fopen(filename, mode), NULL);
	return fp;
}

static size_t
file_size(FILE *fp)
{
	size_t size;

	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	rewind(fp);
	return size;
}

int
ort_dat_load_to(Ort_dat *dat, Ort_base *base, uuid_t id)
{
	FILE *fp = ort_file(base, id, "rb");

	pcheck(fp, 0);
	dat->base = base;
	uuid_copy(dat->id, id);
	dat->size = file_size(fp);
	dat->freeable = 0;
	fread(dat->mem, 1, dat->size, fp);
	fclose(fp);
	return hmap_add(&base->map, id, sizeof(dat->id), dat, &base->stack);
}

Ort_dat *
ort_dat_load(Ort_base *base, uuid_t id)
{
	FILE *fp = ort_file(base, id, "rb");
	size_t size;
	Ort_dat *dat;

	pcheck(fp, 0);
	size = file_size(fp);
	dat = malloc(sizeof(*dat) + size);
	pcheck_c(dat, NULL, fclose(fp));

        if (!hmap_add(&base->map, id, sizeof(dat->id), dat, &base->stack)) {
		fclose(fp);
		free(dat);
		return NULL;
	}

	dat->base = base;
	uuid_copy(dat->id, id);
	dat->size = size;
	dat->freeable = 1;
	fread(dat->mem, 1, size, fp);
	fclose(fp);
	return dat;
}

Ort_ptr
ort_load_to(Ort_base *base, void *mem, uuid_t id)
{
	if (!ort_dat_load_to(mem, base, id))
		return NULL;

	return ((Ort_dat *) mem) + 1;
}

Ort_ptr
ort_load(Ort_base *base, uuid_t id)
{
	Ort_dat *dat = ort_dat_load(base, id);

	pcheck(dat, NULL);
	return dat + 1;
}

int
ort_dat_save(Ort_dat *dat)
{
	FILE *fp = ort_file(dat->base, dat->id, "wb");

	pcheck(fp, 0);
	fwrite(dat->mem, 1, dat->size, fp);
	fclose(fp);
	return 1;
}

int
ort_save(Ort_ptr mem)
{
	return ort_dat_save(ort_dat(mem));
}

Ort_ptr
ort_ptr(Ort_base *base, uuid_t id)
{
	return hmap_get(&base->map, id, sizeof(uuid_t));
}

void
ort_id(uuid_t id, Ort_ptr mem)
{
	uuid_copy(id, ort_dat(mem)->id);
}

size_t
ort_size(Ort_ptr mem)
{
	return ort_dat(mem)->size;
}

Ort_dat *
ort_dat(Ort_ptr mem)
{
	return ((Ort_dat *) mem) - 1;
}

void
ort_dat_destruct(Ort_dat *dat)
{
	if (dat->freeable)
		ort_dat_free(dat);
	else
		ort_dat_dispose(dat);
}

void
ort_destruct(Ort_ptr mem)
{
	ort_dat_destruct(ort_dat(mem));
}
